using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using converter.template;
using converter.template.item;
using System.Diagnostics;

namespace converter
{
	/// <summary>
	/// A report definition (.rdl) file parser.
	/// </summary>
	public sealed partial class RdlFile
	{
		/// <summary>
		/// Parses a report definition file.
		/// <param name="rdlFileName">A name of an .rdl file to be parsed.</param>
		/// <param name="rdsFiles">A list of .rds file names. These files contain the data source information.</param>
		/// <returns>A template representing the parsed .rdl file. The template is ready to be used by the DocumentBuilder.</returns>
		/// </summary>
	    public Template Parse(string rdlFileName, List<string> rdsFiles)
	    {
	        XDocument xdoc = XDocument.Load(rdlFileName);

	        xmlns = AttrValue<string>(Attr(xdoc.Root, "xmlns"), "");

	        if (xdoc.Root.Name != NsName("Report"))
	            throw new Exception(string.Format("The file '{0}' is not a report definition file.", rdlFileName));

	        // Logically an .rdl file is viewed as a set of distinct sections
	        // - report data
			// - report items
	        // 		- body
	        // 		- page header
	        // 		- page footer
	        // - embedded images
	        // - variables
	        // Perform parsing of those sections here and populate the resulting template.
	        // The order of parsing is not important.

	        Template template = new Template();

            ParseReportData(xdoc, xmlns, rdsFiles, template);
            ParsePageHeader(xdoc, template.Body);
            ParseBody(xdoc, xmlns, template);
            ParsePageFooter(xdoc, template.Body);
            ParseEmbeddedImages(xdoc, xmlns, template);
	        // variables

	        return template;
	    }

	    private void ParseReportParameters(XDocument xdoc, Template template)
	    {
	        // Enumerate report parameters.
	        foreach (XElement rp in AllElems(xdoc.Root, "ReportParameter"))
	            ParseReportParameter(template, rp);
	    }

	    private void ParseReportParameter(Template template, XElement param)
	    {
	        string name = AttrValue<string>(Attr(param, "Name"), null);
	        string typeName = ElemValue<string>(Elem(param, "DataType"), null);

	        bool nullable = ElemValue<bool>(Elem(param, "Nullable"), false);
	        bool allowBlank = ElemValue<bool>(Elem(param, "AllowBlank"), false);
	        bool required = !(nullable || allowBlank);

	        // Default value
	        // TODO: Improve a value reading.
	        XElement defVal = Elem(param, "DefaultValue");
	        object v = null;
	        if (defVal != null)
	        {
	            XElement val = defVal.Descendants(NsName("Value")).First();
	            v = ElemValue<object>(val, null);
	        }

	        // TODO: Skip booleans for now. Maybe use integer type instead.
	        if (typeName.ToLower() != "boolean")
	            template.Variables.Add(new Variable(name, typeName, required, v));
	    }

	    private void ParseDataSets(XDocument xdoc, Template template)
	    {
	        // Enumerate data sets.
	        foreach (XElement ds in AllElems(xdoc.Root, "DataSet"))
	            ParseDataSet(template, ds);
	    }

	    private void ParseDataSet(Template template, XElement ds)
	    {
	        string name = AttrValue<string>(Attr(ds, "Name"), null);
	        DataSet.Query query = null;
	        List<DataSet.Field> fields = new List<DataSet.Field>();

	        XElement xQuery = Elem(ds, "Query");
	        if (xQuery != null)
	        {
	            List<DataSet.QueryParameter> parameters = new List<DataSet.QueryParameter>();

	            XElement xParameters = Elem(xQuery, "QueryParameters");
	            if (xParameters != null)
	            {
	                foreach (XElement xParameter in xParameters.Elements())
	                {
	                    parameters.Add(new DataSet.QueryParameter(AttrValue<string>(Attr(xParameter, "Name"), ""), ElemValue<string>(Elem(xParameter, "Value"), "")));
	                }
	            }

	            query = new DataSet.Query(ElemValue<string>(Elem(xQuery, "DataSourceName"), ""),
	                ElemValue<string>(Elem(xQuery, "CommandText"), ""),
	                parameters);
	        }

	        XElement xFields = Elem(ds, "Fields");
	        if (xFields != null)
	        {
	            foreach (XElement xField in xFields.Elements())
	            {
	                XElement xValue = Elem(xField, "Value");
	                bool isExpression = true;
	                if (xValue == null)
	                {
	                    xValue = Elem(xField, "DataField");
	                    isExpression = false;
	                }
	                fields.Add(new DataSet.Field(AttrValue<string>(Attr(xField, "Name"), ""), ElemValue<string>(xValue, ""), isExpression));
	            }
	        }

	        template.DataSets[name] = new DataSet(name, query, fields);
	    }

	    private XElement FindSingleElement(XElement xParent, string name)
	    {
            // Returns an element by its name.
            // Returns null if element not found.
            // Throws an exception is there is more than one element.

            IEnumerable<XElement> elements = xParent.Descendants(NsName(name));
            if (elements.Count() == 1)
                return elements.Single();
            else if (elements.Count() == 0)
                return null;
            else
                throw new Exception("Expected one element, found more");
	    }

	    private void ParseSection(List<TemplateItem> items,
	        XElement xParent,
	        string sectionName)
	    {
	        XElement xSection = FindSingleElement(xParent, sectionName);
	        if (xSection == null) // stop if no element
	            return;

	        XElement xReportItems = FindSingleElement(xSection, "ReportItems");
	        if (xReportItems == null) // stop if no element
	            return;

	        foreach (XElement xItem in xReportItems.Elements())
	            ParseReportItem(items, xItem);
	    }

	    private void ParsePageHeader(XDocument xdoc, List<TemplateItem> items)
	    {
	        // Page element. According to the specification there must be exactly one Page element
	        // but there are reports without it.
	        IEnumerable<XElement> pages = xdoc.Root.Descendants(NsName("Page"));

	        // Parse header section.
	        if (pages.Count() == 1)
	            ParseSection(items, pages.Single(), "PageHeader");
	    }

	    private void ParsePageFooter(XDocument xdoc, List<TemplateItem> items)
	    {
	        IEnumerable<XElement> pages = xdoc.Root.Descendants(NsName("Page"));
	        if (pages.Count() == 1)
	        {
	            ParseSection(items, pages.Single(), "PageFooter");
	        }
	    }

	    private void ParseReportItem(List<TemplateItem> items,
	        XElement xReportItem)
	    {
	        if (xReportItem.Name == NsName("Rectangle"))
	            ParseRectangle(items, xReportItem);
	        else if (xReportItem.Name == NsName("Textbox"))
	            ParseTextbox(items, xReportItem);
	        else if (xReportItem.Name == NsName("Tablix"))
	            ParseTablix(items, xReportItem);
	        else if (xReportItem.Name == NsName("Image"))
	            ParseImage(items, xReportItem);
	    }

	    private void ParseImage(List<TemplateItem> items, XElement xImage)
	    {
	        string source = xImage.Element(NsName("Source")).Value;
	        string value = xImage.Element(NsName("Value")).Value;

	        if (source == "Embedded")
	        {
	            // Value is a name of an embedded image.
	            // The image item will reffer to an embedded image in the same template.
	            Image image = new Image(value);
	            items.Add(image);
	        }
	    }

	    private void ParseRectangle(List<TemplateItem> items, XElement xRectangle)
	    {
	        Paragraph paragraph = new Paragraph();
	        items.Add(paragraph);

	        XElement xReportItems = xRectangle.Element(NsName("ReportItems"));
            if (xReportItems != null)
	            foreach (XElement xItem in xReportItems.Elements())
	                ParseReportItem(items, xItem);
	    }

        private void ParseTablix(List<TemplateItem> items, XElement xTablix)
        {
            XNamespace ns = xTablix.GetDefaultNamespace();

            List<TableRow> rows = ParseTablixRowHierarchy(xTablix);
            rows = ParseTablixColumnHierarchy(xTablix, rows);
            rows = ParseTablixBody(xTablix, rows);

            // data set name
            string dataSetName;
            XElement xDataSetName = xTablix.Element(ns + "DataSetName");
            if (xDataSetName != null)
                dataSetName = (string)xDataSetName;
            else
                dataSetName = string.Empty;

            Table table = new Table(dataSetName);

            // Set the table's name. This will be used as a vairable name in a forEach tag.
            string tableName = (string)xTablix.Attribute("Name");
            table.ItemName = tableName;

            for (int i = 0; i < rows.Count; i++)
                table.Add(rows[i]);

            ParseStyle(table, xTablix);

            items.Add(new Paragraph());
            items.Add(table);
        }

        private List<TableRow> ParseTablixColumnHierarchy(XElement xTablix, List<TableRow> rows)
	    {
            XNamespace ns = xTablix.GetDefaultNamespace();

            XElement xTablixColumnHierarchy = xTablix.Element(ns + "TablixColumnHierarchy");
            if (xTablixColumnHierarchy == null)
                return rows;

            // number of columns in the table
            IEnumerable<XElement> tablixMembers =
                from x in xTablixColumnHierarchy.Descendants(ns + "TablixMember")
                where x.Element(ns + "TablixMembers") == null
                select x;

            // add columns to each row in the table
            for (int i = 0; i < rows.Count; i++)
            {
                for (int j = 0; j < tablixMembers.Count(); j++)
                {
                    TableCell cell = new TableCell();

                    rows[i].Add(cell);
                }
            }

            // column headers
            IEnumerable<XElement> tablixHeaders =
                from x in xTablixColumnHierarchy.Descendants(ns + "TablixHeader")
                select x;

            if (tablixHeaders.Count() > 0)
            {
                // a row of column headers
                TableRow columnHeaders = new TableRow();
                foreach (XElement xTablixHeader in tablixHeaders)
                    ParseCellContents(xTablixHeader, columnHeaders.Items);

                // add a row if needed for column headers to the table rows
                if (rows.Count == 0 || rows[0].IsBodyRow)
                {
                    TableRow headerRow = new TableRow();
                    int cellsToAdd = rows.Count == 0 ? tablixMembers.Count() : rows[0].Items.Count;
                    for (int j = 0; j < cellsToAdd; j++)
                        headerRow.Add(new TableCell() { IsBodyCell = false });

                    rows.Insert(0, headerRow);
                }

                // first column to apply a header to
                int columnIdx = rows[0].Items.Count - tablixMembers.Count();
                for (int i = 0; i < tablixHeaders.Count(); i++)
                {
                    rows[0].Items[columnIdx] = columnHeaders.Items[i];
                }
            }

	        return rows;
	    }

	    private List<TableRow> ParseTablixRowHierarchy(XElement xTablix)
	    {
            List<TableRow> rows = new List<TableRow>();

            XNamespace ns = xTablix.GetDefaultNamespace();

            XElement xTablixRowHierarchy = xTablix.Element(ns + "TablixRowHierarchy");
            if (xTablixRowHierarchy == null)
                return rows; // return empty list

            // number of rows in the table
            IEnumerable<XElement> tablixMembers =
                from x in xTablixRowHierarchy.Descendants(ns + "TablixMember")
                where x.Element(ns + "TablixMembers") == null
                select x;

            foreach (XElement xTablixMember in tablixMembers)
            {
                TableRow row = new TableRow() { IsBodyRow = true };
                row.IsDetail = IsDetail(xTablixMember);

                rows.Add(row);
            }

            // row headers
            IEnumerable<XElement> tablixHeaders =
                from x in xTablixRowHierarchy.Descendants(ns + "TablixHeader")
                select x;

            for (int i = 0; i < tablixHeaders.Count() - tablixMembers.Count(); i++)
                rows.Insert(0, new TableRow());

            // create row header cell
            for (int i = 0; i < tablixHeaders.Count(); i++)
            {
                ParseCellContents(tablixHeaders.ElementAt(i), rows[i].Items);
                (rows[i].Items[0] as TableCell).IsBodyCell = false;
            }

	        return rows;
	    }

        private bool IsDetail(XElement xTablixMember)
        {
            XNamespace ns = xTablixMember.GetDefaultNamespace();

            // Determine if a TablixMember contains Group child element.
            IEnumerable<XElement> groups =
                from x in xTablixMember.Elements(ns + "Group")
                //where x.Element(ns + "GroupExpressions") == null
                select x;

            // If there is no Group child, search for one in a list of parents
            // up to a TablixRowHierarchy element.
            if (groups.Count() == 0)
            {
                XElement xParent = xTablixMember.Parent;
                while (xParent.Name.LocalName != "TablixRowHierarchy" && groups.Count() == 0)
                {
                    groups =
                        from x in xParent.Elements(ns + "Group")
                        //where x.Element(ns + "GroupExpressions") == null
                        select x;
                    xParent = xParent.Parent;
                }
                return groups.Count() > 0;
            }
            else
            {
                return true;
            }
        }

        //private List<TableRow> ParseTablixCorner(XElement xTablix)
        //{
        //    List<TableRow> corner = new List<TableRow>();

        //    XElement xTC = Elem(xTablix, "TablixCorner");
        //    if (xTC != null)
        //    {
        //        foreach (XElement xTCR in Elem(xTC, "TablixCornerRows").Elements())
        //        {
        //            TableRow row = new TableRow(true);
        //            foreach (XElement xTCC in xTCR.Elements())
        //                ParseCellContents(xTCC, row.Items);
        //            corner.Add(row);
        //        }
        //    }

        //    return corner;
        //}

        /// <summary>
        /// Parses the TablixBody element.
        /// </summary>
        /// <param name="xTablix">The parent Tablix element.</param>
        /// <returns>A list of recognized body rows.</returns>
        private List<TableRow> ParseTablixBody(XElement xTablix, List<TableRow> rows)
        {
            XNamespace ns = xTablix.GetDefaultNamespace();

            // Find TablixBody
            XElement xTablixBody = xTablix.Element(ns + "TablixBody");
            if (xTablixBody != null)
            {
                // The table contains rows and columns.
                // Fill in the cells starting at row rowIdx and column colIdx.

                int rowIdx = rows[0].IsBodyRow ? 0 : 1;
                int colIdx = (rows[rowIdx].Items[0] as TableCell).IsBodyCell ? 0 : 1;

                // Go through each TablixRow.
                IEnumerable<XElement> tablixRows = xTablixBody.Element(ns + "TablixRows").Elements(ns + "TablixRow");
                foreach (XElement xTablixRow in tablixRows)
                {
                    // Read cells of the row.
                    TableRow row = new TableRow();
                    IEnumerable<XElement> tablixCells = xTablixRow.Element(ns + "TablixCells").Elements(ns + "TablixCell");
                    foreach (XElement xTablixCell in tablixCells)
                        ParseCellContents(xTablixCell, row.Items);

                    // Copy cells to the table.
                    int i = colIdx;
                    foreach (TableCell cell in row.Items)
                        rows[rowIdx].Items[i++] = cell;
                    rowIdx++;
                }
            }

            return rows;
        }

	    private void ParseCellContents(XElement xTablixCell, List<TemplateItem> items)
	    {
	        XElement xCC = Elem(xTablixCell, "CellContents");
	        if (xCC != null)
	        {
	            TableCell cell = new TableCell();

	            foreach (XElement xCCC in xCC.Elements())
	                ParseReportItem(cell.Items, xCCC);

	            XElement xCS = Elem(xCC, "ColSpan");
	            if (xCS != null)
	                cell.SetColSpan((int)xCS);

                // Find a style of the cell.
                XElement textBox = xCC.Elements().Where(x => x.Name.LocalName == "Textbox").SingleOrDefault();
                if (textBox != null)
                    ParseStyle(cell, textBox);

	            items.Add(cell);
	        }
	    }

	    // Parse every paragraph found in the text box.
	    private void ParseTextbox(List<TemplateItem> items, XElement textBox)
	    {
	        // Textbox contains a list of paragraphs.
	        // Get Paragraphs element.
	        XElement paragraphs = Elem(textBox, "Paragraphs");
	        if (paragraphs != null)
	        {
	            foreach (XElement paragraph in paragraphs.Elements())
	                ParseParagraph(items, paragraph);
	        }
	    }

	    // Create a paragraph template item and
	    // parse every text run found in the paragraph element.
	    private void ParseParagraph(List<TemplateItem> items, XElement xParagraph)
	    {
	        IEnumerable<XElement> textRuns = Elem(xParagraph, "TextRuns").Elements();

	        Paragraph paragraph = new Paragraph();
	        items.Add(paragraph);

	        foreach (XElement textRun in textRuns)
	            ParseTextRun(paragraph.Items, textRun);
	    }

	    // Add a text run template item to the paragraph.
	    private void ParseTextRun(List<TemplateItem> items, XElement trElem)
	    {
	        XElement valElem = Elem(trElem, "Value");
	        TextRun.EvaluationMode ev = GetEvaluationMode(valElem);

	        TextRun tr = new TextRun(valElem.Value, ev);
	        ParseStyle(tr, trElem);

	        items.Add(tr);
	    }

	    // Return an evaluation mode of the value.
	    private TextRun.EvaluationMode GetEvaluationMode(XElement valElem)
	    {
	        // Stay with Auto by default.
	        TextRun.EvaluationMode em = TextRun.EvaluationMode.Auto;
	        XAttribute emAttr = valElem.Attribute(NsName("EvaluationMode"));
	        if (emAttr != null)
	        {
	            if (emAttr.Value == "Expression")
	                em = TextRun.EvaluationMode.Expression;
	            else if (emAttr.Value == "Constant")
	                em = TextRun.EvaluationMode.Constant;
	        }
	        return em;
	    }

	    private void ParseStyle(TemplateItem item, XElement elem)
	    {
	        XElement style = Elem(elem, "Style");
	        if (style != null)
	        {
                // Every item has a style associated with it.

                // Font
                item.GetStyle().Font = new Font(style);

                // Borders
                item.GetStyle().Borders.Border = new Border(style, "Border");
                if (style.Elements().Where(x => x.Name.LocalName == "TopBorder").SingleOrDefault() != null)
                    item.GetStyle().Borders.TopBorder = new Border(style, "TopBorder");
                else
                    item.GetStyle().Borders.TopBorder = item.GetStyle().Borders.Border;
                if (style.Elements().Where(x => x.Name.LocalName == "BottomBorder").SingleOrDefault() != null)
                    item.GetStyle().Borders.TopBorder = new Border(style, "BottomBorder");
                else
                    item.GetStyle().Borders.TopBorder = item.GetStyle().Borders.Border;
                if (style.Elements().Where(x => x.Name.LocalName == "LeftBorder").SingleOrDefault() != null)
                    item.GetStyle().Borders.TopBorder = new Border(style, "LeftBorder");
                else
                    item.GetStyle().Borders.TopBorder = item.GetStyle().Borders.Border;
                if (style.Elements().Where(x => x.Name.LocalName == "RightBorder").SingleOrDefault() != null)
                    item.GetStyle().Borders.TopBorder = new Border(style, "RightBorder");
                else
                    item.GetStyle().Borders.TopBorder = item.GetStyle().Borders.Border;
	        }
	    }

	    private static T ElemValue<T>(XElement elem, T defValue)
	    {
	        T v;
	        if (elem != null)
	            v = (T)Convert.ChangeType(elem.Value, typeof(T));
	        else
	            v = defValue;
	        return v;
	    }

	    private static T AttrValue<T>(XAttribute attr, T defValue)
	    {
	        T v;
	        if (attr != null)
	            v = (T)Convert.ChangeType(attr.Value, typeof(T));
	        else
	            v = defValue;
	        return v;
	    }

	    private static XAttribute Attr(XElement elem, string name)
	    {
	        return elem.Attribute(name);
	    }

	    private XElement Elem(XElement parent, string name)
	    {
	        return parent.Element(NsName(name));
	    }

	    private XName NsName(string name)
	    {
	        return XName.Get(name, xmlns);
	    }

	    private IEnumerable<XElement> AllElems(XElement parent, string name)
	    {
	        return parent.Descendants(NsName(name));
	    }

	    private string xmlns;
	}
}